1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.base;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import com.google.common.annotations.Beta;
23 import com.google.common.annotations.GwtCompatible;
24
25 import java.util.Arrays;
26
27 import javax.annotation.CheckReturnValue;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 @Beta
54 @GwtCompatible(emulated = true)
55 public abstract class CharMatcher implements Predicate<Character> {
56
57
58
59
60
61
62
63
64
65 public static final CharMatcher BREAKING_WHITESPACE = new CharMatcher() {
66 @Override
67 public boolean matches(char c) {
68 switch (c) {
69 case '\t':
70 case '\n':
71 case '\013':
72 case '\f':
73 case '\r':
74 case ' ':
75 case '\u0085':
76 case '\u1680':
77 case '\u2028':
78 case '\u2029':
79 case '\u205f':
80 case '\u3000':
81 return true;
82 case '\u2007':
83 return false;
84 default:
85 return c >= '\u2000' && c <= '\u200a';
86 }
87 }
88
89 @Override
90 public String toString() {
91 return "CharMatcher.BREAKING_WHITESPACE";
92 }
93 };
94
95
96
97
98 public static final CharMatcher ASCII = inRange('\0', '\u007f', "CharMatcher.ASCII");
99
100 private static class RangesMatcher extends CharMatcher {
101 private final char[] rangeStarts;
102 private final char[] rangeEnds;
103
104 RangesMatcher(String description, char[] rangeStarts, char[] rangeEnds) {
105 super(description);
106 this.rangeStarts = rangeStarts;
107 this.rangeEnds = rangeEnds;
108 checkArgument(rangeStarts.length == rangeEnds.length);
109 for (int i = 0; i < rangeStarts.length; i++) {
110 checkArgument(rangeStarts[i] <= rangeEnds[i]);
111 if (i + 1 < rangeStarts.length) {
112 checkArgument(rangeEnds[i] < rangeStarts[i + 1]);
113 }
114 }
115 }
116
117 @Override
118 public boolean matches(char c) {
119 int index = Arrays.binarySearch(rangeStarts, c);
120 if (index >= 0) {
121 return true;
122 } else {
123 index = ~index - 1;
124 return index >= 0 && c <= rangeEnds[index];
125 }
126 }
127 }
128
129
130 private static final String ZEROES = "0\u0660\u06f0\u07c0\u0966\u09e6\u0a66\u0ae6\u0b66\u0be6"
131 + "\u0c66\u0ce6\u0d66\u0e50\u0ed0\u0f20\u1040\u1090\u17e0\u1810\u1946\u19d0\u1b50\u1bb0"
132 + "\u1c40\u1c50\ua620\ua8d0\ua900\uaa50\uff10";
133
134 private static final String NINES;
135 static {
136 StringBuilder builder = new StringBuilder(ZEROES.length());
137 for (int i = 0; i < ZEROES.length(); i++) {
138 builder.append((char) (ZEROES.charAt(i) + 9));
139 }
140 NINES = builder.toString();
141 }
142
143
144
145
146
147
148 public static final CharMatcher DIGIT = new RangesMatcher(
149 "CharMatcher.DIGIT", ZEROES.toCharArray(), NINES.toCharArray());
150
151
152
153
154
155
156 public static final CharMatcher JAVA_DIGIT = new CharMatcher("CharMatcher.JAVA_DIGIT") {
157 @Override public boolean matches(char c) {
158 return Character.isDigit(c);
159 }
160 };
161
162
163
164
165
166
167 public static final CharMatcher JAVA_LETTER = new CharMatcher("CharMatcher.JAVA_LETTER") {
168 @Override public boolean matches(char c) {
169 return Character.isLetter(c);
170 }
171 };
172
173
174
175
176
177 public static final CharMatcher JAVA_LETTER_OR_DIGIT =
178 new CharMatcher("CharMatcher.JAVA_LETTER_OR_DIGIT") {
179 @Override public boolean matches(char c) {
180 return Character.isLetterOrDigit(c);
181 }
182 };
183
184
185
186
187
188 public static final CharMatcher JAVA_UPPER_CASE =
189 new CharMatcher("CharMatcher.JAVA_UPPER_CASE") {
190 @Override public boolean matches(char c) {
191 return Character.isUpperCase(c);
192 }
193 };
194
195
196
197
198
199 public static final CharMatcher JAVA_LOWER_CASE =
200 new CharMatcher("CharMatcher.JAVA_LOWER_CASE") {
201 @Override public boolean matches(char c) {
202 return Character.isLowerCase(c);
203 }
204 };
205
206
207
208
209
210 public static final CharMatcher JAVA_ISO_CONTROL =
211 inRange('\u0000', '\u001f')
212 .or(inRange('\u007f', '\u009f'))
213 .withToString("CharMatcher.JAVA_ISO_CONTROL");
214
215
216
217
218
219
220 public static final CharMatcher INVISIBLE = new RangesMatcher("CharMatcher.INVISIBLE", (
221 "\u0000\u007f\u00ad\u0600\u061c\u06dd\u070f\u1680\u180e\u2000\u2028\u205f\u2066\u2067\u2068"
222 + "\u2069\u206a\u3000\ud800\ufeff\ufff9\ufffa").toCharArray(), (
223 "\u0020\u00a0\u00ad\u0604\u061c\u06dd\u070f\u1680\u180e\u200f\u202f\u2064\u2066\u2067\u2068"
224 + "\u2069\u206f\u3000\uf8ff\ufeff\ufff9\ufffb").toCharArray());
225
226 private static String showCharacter(char c) {
227 String hex = "0123456789ABCDEF";
228 char[] tmp = {'\\', 'u', '\0', '\0', '\0', '\0'};
229 for (int i = 0; i < 4; i++) {
230 tmp[5 - i] = hex.charAt(c & 0xF);
231 c >>= 4;
232 }
233 return String.copyValueOf(tmp);
234
235 }
236
237
238
239
240
241
242
243
244
245 public static final CharMatcher SINGLE_WIDTH = new RangesMatcher("CharMatcher.SINGLE_WIDTH",
246 "\u0000\u05be\u05d0\u05f3\u0600\u0750\u0e00\u1e00\u2100\ufb50\ufe70\uff61".toCharArray(),
247 "\u04f9\u05be\u05ea\u05f4\u06ff\u077f\u0e7f\u20af\u213a\ufdff\ufeff\uffdc".toCharArray());
248
249
250 public static final CharMatcher ANY =
251 new FastMatcher("CharMatcher.ANY") {
252 @Override public boolean matches(char c) {
253 return true;
254 }
255
256 @Override public int indexIn(CharSequence sequence) {
257 return (sequence.length() == 0) ? -1 : 0;
258 }
259
260 @Override public int indexIn(CharSequence sequence, int start) {
261 int length = sequence.length();
262 Preconditions.checkPositionIndex(start, length);
263 return (start == length) ? -1 : start;
264 }
265
266 @Override public int lastIndexIn(CharSequence sequence) {
267 return sequence.length() - 1;
268 }
269
270 @Override public boolean matchesAllOf(CharSequence sequence) {
271 checkNotNull(sequence);
272 return true;
273 }
274
275 @Override public boolean matchesNoneOf(CharSequence sequence) {
276 return sequence.length() == 0;
277 }
278
279 @Override public String removeFrom(CharSequence sequence) {
280 checkNotNull(sequence);
281 return "";
282 }
283
284 @Override public String replaceFrom(CharSequence sequence, char replacement) {
285 char[] array = new char[sequence.length()];
286 Arrays.fill(array, replacement);
287 return new String(array);
288 }
289
290 @Override public String replaceFrom(CharSequence sequence, CharSequence replacement) {
291 StringBuilder retval = new StringBuilder(sequence.length() * replacement.length());
292 for (int i = 0; i < sequence.length(); i++) {
293 retval.append(replacement);
294 }
295 return retval.toString();
296 }
297
298 @Override public String collapseFrom(CharSequence sequence, char replacement) {
299 return (sequence.length() == 0) ? "" : String.valueOf(replacement);
300 }
301
302 @Override public String trimFrom(CharSequence sequence) {
303 checkNotNull(sequence);
304 return "";
305 }
306
307 @Override public int countIn(CharSequence sequence) {
308 return sequence.length();
309 }
310
311 @Override public CharMatcher and(CharMatcher other) {
312 return checkNotNull(other);
313 }
314
315 @Override public CharMatcher or(CharMatcher other) {
316 checkNotNull(other);
317 return this;
318 }
319
320 @Override public CharMatcher negate() {
321 return NONE;
322 }
323 };
324
325
326 public static final CharMatcher NONE =
327 new FastMatcher("CharMatcher.NONE") {
328 @Override public boolean matches(char c) {
329 return false;
330 }
331
332 @Override public int indexIn(CharSequence sequence) {
333 checkNotNull(sequence);
334 return -1;
335 }
336
337 @Override public int indexIn(CharSequence sequence, int start) {
338 int length = sequence.length();
339 Preconditions.checkPositionIndex(start, length);
340 return -1;
341 }
342
343 @Override public int lastIndexIn(CharSequence sequence) {
344 checkNotNull(sequence);
345 return -1;
346 }
347
348 @Override public boolean matchesAllOf(CharSequence sequence) {
349 return sequence.length() == 0;
350 }
351
352 @Override public boolean matchesNoneOf(CharSequence sequence) {
353 checkNotNull(sequence);
354 return true;
355 }
356
357 @Override public String removeFrom(CharSequence sequence) {
358 return sequence.toString();
359 }
360
361 @Override public String replaceFrom(CharSequence sequence, char replacement) {
362 return sequence.toString();
363 }
364
365 @Override public String replaceFrom(CharSequence sequence, CharSequence replacement) {
366 checkNotNull(replacement);
367 return sequence.toString();
368 }
369
370 @Override public String collapseFrom(CharSequence sequence, char replacement) {
371 return sequence.toString();
372 }
373
374 @Override public String trimFrom(CharSequence sequence) {
375 return sequence.toString();
376 }
377
378 @Override
379 public String trimLeadingFrom(CharSequence sequence) {
380 return sequence.toString();
381 }
382
383 @Override
384 public String trimTrailingFrom(CharSequence sequence) {
385 return sequence.toString();
386 }
387
388 @Override public int countIn(CharSequence sequence) {
389 checkNotNull(sequence);
390 return 0;
391 }
392
393 @Override public CharMatcher and(CharMatcher other) {
394 checkNotNull(other);
395 return this;
396 }
397
398 @Override public CharMatcher or(CharMatcher other) {
399 return checkNotNull(other);
400 }
401
402 @Override public CharMatcher negate() {
403 return ANY;
404 }
405 };
406
407
408
409
410
411
412 public static CharMatcher is(final char match) {
413 String description = "CharMatcher.is('" + showCharacter(match) + "')";
414 return new FastMatcher(description) {
415 @Override public boolean matches(char c) {
416 return c == match;
417 }
418
419 @Override public String replaceFrom(CharSequence sequence, char replacement) {
420 return sequence.toString().replace(match, replacement);
421 }
422
423 @Override public CharMatcher and(CharMatcher other) {
424 return other.matches(match) ? this : NONE;
425 }
426
427 @Override public CharMatcher or(CharMatcher other) {
428 return other.matches(match) ? other : super.or(other);
429 }
430
431 @Override public CharMatcher negate() {
432 return isNot(match);
433 }
434 };
435 }
436
437
438
439
440
441
442 public static CharMatcher isNot(final char match) {
443 String description = "CharMatcher.isNot('" + showCharacter(match) + "')";
444 return new FastMatcher(description) {
445 @Override public boolean matches(char c) {
446 return c != match;
447 }
448
449 @Override public CharMatcher and(CharMatcher other) {
450 return other.matches(match) ? super.and(other) : other;
451 }
452
453 @Override public CharMatcher or(CharMatcher other) {
454 return other.matches(match) ? ANY : this;
455 }
456
457 @Override public CharMatcher negate() {
458 return is(match);
459 }
460 };
461 }
462
463
464
465
466
467 public static CharMatcher anyOf(final CharSequence sequence) {
468 switch (sequence.length()) {
469 case 0:
470 return NONE;
471 case 1:
472 return is(sequence.charAt(0));
473 case 2:
474 return isEither(sequence.charAt(0), sequence.charAt(1));
475 default:
476
477 }
478
479 final char[] chars = sequence.toString().toCharArray();
480 Arrays.sort(chars);
481 StringBuilder description = new StringBuilder("CharMatcher.anyOf(\"");
482 for (char c : chars) {
483 description.append(showCharacter(c));
484 }
485 description.append("\")");
486 return new CharMatcher(description.toString()) {
487 @Override public boolean matches(char c) {
488 return Arrays.binarySearch(chars, c) >= 0;
489 }
490 };
491 }
492
493 private static CharMatcher isEither(
494 final char match1,
495 final char match2) {
496 String description = "CharMatcher.anyOf(\"" +
497 showCharacter(match1) + showCharacter(match2) + "\")";
498 return new FastMatcher(description) {
499 @Override public boolean matches(char c) {
500 return c == match1 || c == match2;
501 }
502 };
503 }
504
505
506
507
508
509 public static CharMatcher noneOf(CharSequence sequence) {
510 return anyOf(sequence).negate();
511 }
512
513
514
515
516
517
518
519
520 public static CharMatcher inRange(final char startInclusive, final char endInclusive) {
521 checkArgument(endInclusive >= startInclusive);
522 String description = "CharMatcher.inRange('" +
523 showCharacter(startInclusive) + "', '" +
524 showCharacter(endInclusive) + "')";
525 return inRange(startInclusive, endInclusive, description);
526 }
527
528 static CharMatcher inRange(final char startInclusive, final char endInclusive,
529 String description) {
530 return new FastMatcher(description) {
531 @Override public boolean matches(char c) {
532 return startInclusive <= c && c <= endInclusive;
533 }
534 };
535 }
536
537
538
539
540
541 public static CharMatcher forPredicate(final Predicate<? super Character> predicate) {
542 checkNotNull(predicate);
543 if (predicate instanceof CharMatcher) {
544 return (CharMatcher) predicate;
545 }
546 String description = "CharMatcher.forPredicate(" + predicate + ")";
547 return new CharMatcher(description) {
548 @Override public boolean matches(char c) {
549 return predicate.apply(c);
550 }
551
552 @Override public boolean apply(Character character) {
553 return predicate.apply(checkNotNull(character));
554 }
555 };
556 }
557
558
559 final String description;
560
561
562
563
564
565
566 CharMatcher(String description) {
567 this.description = description;
568 }
569
570
571
572
573
574 protected CharMatcher() {
575 description = super.toString();
576 }
577
578
579
580
581 public abstract boolean matches(char c);
582
583
584
585
586
587
588 public CharMatcher negate() {
589 return new NegatedMatcher(this);
590 }
591
592 private static class NegatedMatcher extends CharMatcher {
593 final CharMatcher original;
594
595 NegatedMatcher(String toString, CharMatcher original) {
596 super(toString);
597 this.original = original;
598 }
599
600 NegatedMatcher(CharMatcher original) {
601 this(original + ".negate()", original);
602 }
603
604 @Override public boolean matches(char c) {
605 return !original.matches(c);
606 }
607
608 @Override public boolean matchesAllOf(CharSequence sequence) {
609 return original.matchesNoneOf(sequence);
610 }
611
612 @Override public boolean matchesNoneOf(CharSequence sequence) {
613 return original.matchesAllOf(sequence);
614 }
615
616 @Override public int countIn(CharSequence sequence) {
617 return sequence.length() - original.countIn(sequence);
618 }
619
620 @Override public CharMatcher negate() {
621 return original;
622 }
623
624 @Override
625 CharMatcher withToString(String description) {
626 return new NegatedMatcher(description, original);
627 }
628 }
629
630
631
632
633 public CharMatcher and(CharMatcher other) {
634 return new And(this, checkNotNull(other));
635 }
636
637 private static class And extends CharMatcher {
638 final CharMatcher first;
639 final CharMatcher second;
640
641 And(CharMatcher a, CharMatcher b) {
642 this(a, b, "CharMatcher.and(" + a + ", " + b + ")");
643 }
644
645 And(CharMatcher a, CharMatcher b, String description) {
646 super(description);
647 first = checkNotNull(a);
648 second = checkNotNull(b);
649 }
650
651 @Override
652 public boolean matches(char c) {
653 return first.matches(c) && second.matches(c);
654 }
655
656 @Override
657 CharMatcher withToString(String description) {
658 return new And(first, second, description);
659 }
660 }
661
662
663
664
665 public CharMatcher or(CharMatcher other) {
666 return new Or(this, checkNotNull(other));
667 }
668
669 private static class Or extends CharMatcher {
670 final CharMatcher first;
671 final CharMatcher second;
672
673 Or(CharMatcher a, CharMatcher b, String description) {
674 super(description);
675 first = checkNotNull(a);
676 second = checkNotNull(b);
677 }
678
679 Or(CharMatcher a, CharMatcher b) {
680 this(a, b, "CharMatcher.or(" + a + ", " + b + ")");
681 }
682
683 @Override
684 public boolean matches(char c) {
685 return first.matches(c) || second.matches(c);
686 }
687
688 @Override
689 CharMatcher withToString(String description) {
690 return new Or(first, second, description);
691 }
692 }
693
694
695
696
697
698
699
700
701
702
703 public CharMatcher precomputed() {
704 return Platform.precomputeCharMatcher(this);
705 }
706
707
708
709
710
711
712
713 CharMatcher withToString(String description) {
714 throw new UnsupportedOperationException();
715 }
716
717 private static final int DISTINCT_CHARS = Character.MAX_VALUE - Character.MIN_VALUE + 1;
718
719
720
721
722 abstract static class FastMatcher extends CharMatcher {
723 FastMatcher() {
724 super();
725 }
726
727 FastMatcher(String description) {
728 super(description);
729 }
730
731 @Override
732 public final CharMatcher precomputed() {
733 return this;
734 }
735
736 @Override
737 public CharMatcher negate() {
738 return new NegatedFastMatcher(this);
739 }
740 }
741
742 static final class NegatedFastMatcher extends NegatedMatcher {
743 NegatedFastMatcher(CharMatcher original) {
744 super(original);
745 }
746
747 NegatedFastMatcher(String toString, CharMatcher original) {
748 super(toString, original);
749 }
750
751 @Override
752 public final CharMatcher precomputed() {
753 return this;
754 }
755
756 @Override
757 CharMatcher withToString(String description) {
758 return new NegatedFastMatcher(description, original);
759 }
760 }
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775 public boolean matchesAnyOf(CharSequence sequence) {
776 return !matchesNoneOf(sequence);
777 }
778
779
780
781
782
783
784
785
786
787
788
789 public boolean matchesAllOf(CharSequence sequence) {
790 for (int i = sequence.length() - 1; i >= 0; i--) {
791 if (!matches(sequence.charAt(i))) {
792 return false;
793 }
794 }
795 return true;
796 }
797
798
799
800
801
802
803
804
805
806
807
808
809 public boolean matchesNoneOf(CharSequence sequence) {
810 return indexIn(sequence) == -1;
811 }
812
813
814
815
816
817
818
819
820
821
822
823 public int indexIn(CharSequence sequence) {
824 int length = sequence.length();
825 for (int i = 0; i < length; i++) {
826 if (matches(sequence.charAt(i))) {
827 return i;
828 }
829 }
830 return -1;
831 }
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848 public int indexIn(CharSequence sequence, int start) {
849 int length = sequence.length();
850 Preconditions.checkPositionIndex(start, length);
851 for (int i = start; i < length; i++) {
852 if (matches(sequence.charAt(i))) {
853 return i;
854 }
855 }
856 return -1;
857 }
858
859
860
861
862
863
864
865
866
867
868
869 public int lastIndexIn(CharSequence sequence) {
870 for (int i = sequence.length() - 1; i >= 0; i--) {
871 if (matches(sequence.charAt(i))) {
872 return i;
873 }
874 }
875 return -1;
876 }
877
878
879
880
881 public int countIn(CharSequence sequence) {
882 int count = 0;
883 for (int i = 0; i < sequence.length(); i++) {
884 if (matches(sequence.charAt(i))) {
885 count++;
886 }
887 }
888 return count;
889 }
890
891
892
893
894
895
896
897
898
899 @CheckReturnValue
900 public String removeFrom(CharSequence sequence) {
901 String string = sequence.toString();
902 int pos = indexIn(string);
903 if (pos == -1) {
904 return string;
905 }
906
907 char[] chars = string.toCharArray();
908 int spread = 1;
909
910
911 OUT: while (true) {
912 pos++;
913 while (true) {
914 if (pos == chars.length) {
915 break OUT;
916 }
917 if (matches(chars[pos])) {
918 break;
919 }
920 chars[pos - spread] = chars[pos];
921 pos++;
922 }
923 spread++;
924 }
925 return new String(chars, 0, pos - spread);
926 }
927
928
929
930
931
932
933
934
935
936 @CheckReturnValue
937 public String retainFrom(CharSequence sequence) {
938 return negate().removeFrom(sequence);
939 }
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958 @CheckReturnValue
959 public String replaceFrom(CharSequence sequence, char replacement) {
960 String string = sequence.toString();
961 int pos = indexIn(string);
962 if (pos == -1) {
963 return string;
964 }
965 char[] chars = string.toCharArray();
966 chars[pos] = replacement;
967 for (int i = pos + 1; i < chars.length; i++) {
968 if (matches(chars[i])) {
969 chars[i] = replacement;
970 }
971 }
972 return new String(chars);
973 }
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991 @CheckReturnValue
992 public String replaceFrom(CharSequence sequence, CharSequence replacement) {
993 int replacementLen = replacement.length();
994 if (replacementLen == 0) {
995 return removeFrom(sequence);
996 }
997 if (replacementLen == 1) {
998 return replaceFrom(sequence, replacement.charAt(0));
999 }
1000
1001 String string = sequence.toString();
1002 int pos = indexIn(string);
1003 if (pos == -1) {
1004 return string;
1005 }
1006
1007 int len = string.length();
1008 StringBuilder buf = new StringBuilder((len * 3 / 2) + 16);
1009
1010 int oldpos = 0;
1011 do {
1012 buf.append(string, oldpos, pos);
1013 buf.append(replacement);
1014 oldpos = pos + 1;
1015 pos = indexIn(string, oldpos);
1016 } while (pos != -1);
1017
1018 buf.append(string, oldpos, len);
1019 return buf.toString();
1020 }
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036 @CheckReturnValue
1037 public String trimFrom(CharSequence sequence) {
1038 int len = sequence.length();
1039 int first;
1040 int last;
1041
1042 for (first = 0; first < len; first++) {
1043 if (!matches(sequence.charAt(first))) {
1044 break;
1045 }
1046 }
1047 for (last = len - 1; last > first; last--) {
1048 if (!matches(sequence.charAt(last))) {
1049 break;
1050 }
1051 }
1052
1053 return sequence.subSequence(first, last + 1).toString();
1054 }
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064 @CheckReturnValue
1065 public String trimLeadingFrom(CharSequence sequence) {
1066 int len = sequence.length();
1067 for (int first = 0; first < len; first++) {
1068 if (!matches(sequence.charAt(first))) {
1069 return sequence.subSequence(first, len).toString();
1070 }
1071 }
1072 return "";
1073 }
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083 @CheckReturnValue
1084 public String trimTrailingFrom(CharSequence sequence) {
1085 int len = sequence.length();
1086 for (int last = len - 1; last >= 0; last--) {
1087 if (!matches(sequence.charAt(last))) {
1088 return sequence.subSequence(0, last + 1).toString();
1089 }
1090 }
1091 return "";
1092 }
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112 @CheckReturnValue
1113 public String collapseFrom(CharSequence sequence, char replacement) {
1114
1115 int len = sequence.length();
1116 for (int i = 0; i < len; i++) {
1117 char c = sequence.charAt(i);
1118 if (matches(c)) {
1119 if (c == replacement
1120 && (i == len - 1 || !matches(sequence.charAt(i + 1)))) {
1121
1122 i++;
1123 } else {
1124 StringBuilder builder = new StringBuilder(len)
1125 .append(sequence.subSequence(0, i))
1126 .append(replacement);
1127 return finishCollapseFrom(sequence, i + 1, len, replacement, builder, true);
1128 }
1129 }
1130 }
1131
1132 return sequence.toString();
1133 }
1134
1135
1136
1137
1138
1139
1140 @CheckReturnValue
1141 public String trimAndCollapseFrom(CharSequence sequence, char replacement) {
1142
1143 int len = sequence.length();
1144 int first;
1145 int last;
1146
1147 for (first = 0; first < len && matches(sequence.charAt(first)); first++) {}
1148 for (last = len - 1; last > first && matches(sequence.charAt(last)); last--) {}
1149
1150 return (first == 0 && last == len - 1)
1151 ? collapseFrom(sequence, replacement)
1152 : finishCollapseFrom(
1153 sequence, first, last + 1, replacement,
1154 new StringBuilder(last + 1 - first),
1155 false);
1156 }
1157
1158 private String finishCollapseFrom(
1159 CharSequence sequence, int start, int end, char replacement,
1160 StringBuilder builder, boolean inMatchingGroup) {
1161 for (int i = start; i < end; i++) {
1162 char c = sequence.charAt(i);
1163 if (matches(c)) {
1164 if (!inMatchingGroup) {
1165 builder.append(replacement);
1166 inMatchingGroup = true;
1167 }
1168 } else {
1169 builder.append(c);
1170 inMatchingGroup = false;
1171 }
1172 }
1173 return builder.toString();
1174 }
1175
1176
1177
1178
1179
1180 @Deprecated
1181 @Override
1182 public boolean apply(Character character) {
1183 return matches(character);
1184 }
1185
1186
1187
1188
1189
1190 @Override
1191 public String toString() {
1192 return description;
1193 }
1194
1195 static final String WHITESPACE_TABLE = ""
1196 + "\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000"
1197 + "\u2029\u000B\u3000\u2008\u2003\u205F\u3000\u1680"
1198 + "\u0009\u0020\u2006\u2001\u202F\u00A0\u000C\u2009"
1199 + "\u3000\u2004\u3000\u3000\u2028\n\u2007\u3000";
1200 static final int WHITESPACE_MULTIPLIER = 1682554634;
1201 static final int WHITESPACE_SHIFT = Integer.numberOfLeadingZeros(WHITESPACE_TABLE.length() - 1);
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214 public static final CharMatcher WHITESPACE = new FastMatcher("WHITESPACE") {
1215 @Override
1216 public boolean matches(char c) {
1217 return WHITESPACE_TABLE.charAt((WHITESPACE_MULTIPLIER * c) >>> WHITESPACE_SHIFT) == c;
1218 }
1219 };
1220 }